﻿using OpenCvSharp;
using System;
using System.Data;
using System.IO;
using ZXing.Common;
using ZXing;
using Point = OpenCvSharp.Point;
using Size = OpenCvSharp.Size;
using ZXing.Windows.Compatibility;

public static class ImageProcessor
{
    public static void ExtractFillInAreas01(string inputImagePath, string outputDir)
    {
        Directory.CreateDirectory(outputDir);

        Mat src = Cv2.ImRead(inputImagePath, ImreadModes.Color);
        if (src.Empty())
        {
            Console.WriteLine("无法读取图像！");
            return;
        }

        Mat gray = new Mat();
        Cv2.CvtColor(src, gray, ColorConversionCodes.BGR2GRAY);

        Mat binary = new Mat();
        Cv2.Threshold(gray, binary, 0, 255, ThresholdTypes.Otsu | ThresholdTypes.Binary);

        Mat kernel = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(3, 3));
        Cv2.MorphologyEx(binary, binary, MorphTypes.Open, kernel); // 使用 MorphTypes.Open

        Mat labels = new Mat();
        Mat stats = new Mat();
        Mat centroids = new Mat();
        int nLabels = Cv2.ConnectedComponentsWithStats(binary, labels, stats, centroids, PixelConnectivity.Connectivity8);

        List<Rect> fillInAreas = new List<Rect>();

        for (int i = 1; i < nLabels; i++)
        {
            int area = stats.At<int>(i, (int)ConnectedComponentsTypes.Area);
            int width = stats.At<int>(i, (int)ConnectedComponentsTypes.Width);
            int height = stats.At<int>(i, (int)ConnectedComponentsTypes.Height);

            if (area > 500 && width > 100 && height > 100)
            {
                int left = stats.At<int>(i, (int)ConnectedComponentsTypes.Left);
                int top = stats.At<int>(i, (int)ConnectedComponentsTypes.Top);
                fillInAreas.Add(new Rect(left, top, width, height));
            }
        }

        int count = 0;
        foreach (var rect in fillInAreas)
        {
            Mat roi = new Mat(src, rect);
            string outputPath = Path.Combine(outputDir, $"fill_in_area_{count++}.png");
            Cv2.ImWrite(outputPath, roi);
        }

        Mat srcCopy = src.Clone();
        foreach (var rect in fillInAreas)
        {
            Cv2.Rectangle(srcCopy, rect, Scalar.Red, 2);
        }
        string visualPath = Path.Combine(outputDir, "marked_image.png");
        Cv2.ImWrite(visualPath, srcCopy);

        Console.WriteLine($"提取了 {fillInAreas.Count} 个填涂区域。");
    }

    public static void ExtractFillInAreas02(string inputImagePath, string outputDir)
    {
        Directory.CreateDirectory(outputDir);

        Mat src = Cv2.ImRead(inputImagePath, ImreadModes.Color);
        if (src.Empty())
        {
            Console.WriteLine("无法读取图像！");
            return;
        }

        Mat gray = new Mat();
        Cv2.CvtColor(src, gray, ColorConversionCodes.BGR2GRAY);

        Mat binary = new Mat();
        Cv2.AdaptiveThreshold(gray, binary, 255, AdaptiveThresholdTypes.GaussianC, ThresholdTypes.BinaryInv, 15, 10);

        Mat kernel = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(3, 3));
        Cv2.MorphologyEx(binary, binary, MorphTypes.Open, kernel);

        // 细化处理（可选）
        // Mat skeleton = new Mat();
        // Cv2.Ximgproc.Thinning(binary, skeleton);

        Mat labels = new Mat();
        Mat stats = new Mat();
        Mat centroids = new Mat();
        int nLabels = Cv2.ConnectedComponentsWithStats(binary, labels, stats, centroids, PixelConnectivity.Connectivity8);

        List<Rect> fillInAreas = new List<Rect>();

        for (int i = 1; i < nLabels; i++)
        {
            int area = stats.At<int>(i, (int)ConnectedComponentsTypes.Area);
            int width = stats.At<int>(i, (int)ConnectedComponentsTypes.Width);
            int height = stats.At<int>(i, (int)ConnectedComponentsTypes.Height);

            // 调整筛选条件
            if (  width > 20 && height > 10)
            {
                int left = stats.At<int>(i, (int)ConnectedComponentsTypes.Left);
                int top = stats.At<int>(i, (int)ConnectedComponentsTypes.Top);
                fillInAreas.Add(new Rect(left, top, width, height));
            }
        }

        int count = 0;
        foreach (var rect in fillInAreas)
        {
            Mat roi = new Mat(src, rect);
            string outputPath = Path.Combine(outputDir, $"fill_in_area_{count++}.png");
            Cv2.ImWrite(outputPath, roi);
        }

        Mat srcCopy = src.Clone();
        foreach (var rect in fillInAreas)
        {
            Cv2.Rectangle(srcCopy, rect, Scalar.Red, 2);
        }
        string visualPath = Path.Combine(outputDir, "marked_image.png");
        Cv2.ImWrite(visualPath, srcCopy);

        Console.WriteLine($"提取了 {fillInAreas.Count} 个填涂区域。");
    }

    public static Rect ExtractFillInAreas(Mat src, out List<Rect> rs, string outputDir="R:\\Temp")
    {
        Directory.CreateDirectory(outputDir);
        rs= new List<Rect>();
       

        Mat gray = new Mat();
        Cv2.CvtColor(src, gray, ColorConversionCodes.BGR2GRAY);

        Mat binary = new Mat();
        Cv2.AdaptiveThreshold(gray, binary, 255, AdaptiveThresholdTypes.GaussianC, ThresholdTypes.BinaryInv, 15, 10);

        Mat kernel = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(3, 3));
        Cv2.MorphologyEx(binary, binary, MorphTypes.Open, kernel);

        Mat labels = new Mat();
        Mat stats = new Mat();
        Mat centroids = new Mat();
        int nLabels = Cv2.ConnectedComponentsWithStats(binary, labels, stats, centroids, PixelConnectivity.Connectivity8);

        List<Rect> fillInAreas = new List<Rect>();
        List<Point> centroidsList = new List<Point>();

        for (int i = 1; i < nLabels; i++)
        {
            int area = stats.At<int>(i, (int)ConnectedComponentsTypes.Area);
            int width = stats.At<int>(i, (int)ConnectedComponentsTypes.Width);
            int height = stats.At<int>(i, (int)ConnectedComponentsTypes.Height);

            if (width > 20 && height > 10)
            {
                int left = stats.At<int>(i, (int)ConnectedComponentsTypes.Left);
                int top = stats.At<int>(i, (int)ConnectedComponentsTypes.Top);
                fillInAreas.Add(new Rect(left, top, width, height));

                Point centroid = new Point(
                    (int)centroids.At<double>(i, 0),
                    (int)centroids.At<double>(i, 1)
                );
                centroidsList.Add(centroid);
            }
        }

        int clusterCount = 3; // 假设分为 3 类
        Mat data = new Mat(centroidsList.Count, 2, MatType.CV_32FC1);
        for (int i = 0; i < centroidsList.Count; i++)
        {
            data.At<float>(i, 0) = centroidsList[i].X;
            data.At<float>(i, 1) = centroidsList[i].Y;
        }

        Mat labelsOut = new Mat();
        Mat centers = new Mat();

        // 1. 构造终止条件
        var criteria = new TermCriteria(
            
            Type: CriteriaTypes.Count | CriteriaTypes.Eps,
            MaxCount: 100,
            Epsilon: 0.1);

        // 2. K-means 聚类
        double compactness = Cv2.Kmeans(
        data: data,               // 输入：CV_32F，每行一个样本
            k: clusterCount,             // 聚类中心个数
            bestLabels: labelsOut,       // 输出标签
            criteria: criteria,          // 终止条件
            attempts: 10,                // 重复次数
            flags: KMeansFlags.PpCenters,// 初始中心使用 k-means++
            centers: centers);           // 输出中心

        List<Rect>[] clusteredAreas = new List<Rect>[clusterCount];
        for (int i = 0; i < clusterCount; i++)
        {
            clusteredAreas[i] = new List<Rect>();
        }

        for (int i = 0; i < fillInAreas.Count; i++)
        {
            int clusterIdx = labelsOut.At<int>(0, i);
            clusteredAreas[clusterIdx].Add(fillInAreas[i]);
        }

        // 去掉靠近边沿和角落的矩形
        List<Rect> finalAreas = new List<Rect>();
        int borderThreshold = 100; // 边缘阈值

        foreach (var cluster in clusteredAreas)
        {
            foreach (var rect in cluster)
            {
                if (rect.Left > borderThreshold && rect.Right < src.Width - borderThreshold &&
                    rect.Top > borderThreshold && rect.Bottom < src.Height - borderThreshold)
                {
                    finalAreas.Add(rect);
                }
            }
        }
        Rect merged = finalAreas.DefaultIfEmpty(new Rect())
                        .Aggregate((acc, r) => acc.Union(r));


        int count = 0;
        foreach (var rect in finalAreas)
        {
            Mat roi = new Mat(src, rect);
            string outputPath = Path.Combine(outputDir, $"fill_in_area_{count++}.png");
            Cv2.ImWrite(outputPath, roi);
        }

        Mat srcCopy = src.Clone();
        foreach (var rect in finalAreas)
        {
            Cv2.Rectangle(srcCopy, rect, Scalar.Red, 2);
            rs.Add(rect);
        }
        srcCopy.Rectangle(merged, Scalar.Green, 3);
        //
        return merged;

        string visualPath = Path.Combine(outputDir, "marked_image.png");
        Cv2.ImWrite(visualPath, srcCopy);

        Console.WriteLine($"提取了 {finalAreas.Count} 个填涂区域。");
    }

    public static string[] GetFiles()
    {
        string folder = @"D:\Oamist\PICBW150";
        int idx = 0;
        string[] files = Directory.GetFiles(folder, "*.*")
                                  .Where(f => f.EndsWith(".jpg") || f.EndsWith(".png") || f.EndsWith(".bmp"))
                                  .ToArray();
        return files;
    }
    public static string 识别条形码(System.Drawing.Bitmap filename)
    {   //install-package Zxing.net
        //install-package Zxing.net.Bindings.windows.compatibility
        //创建 ZXing.Net 的 BarcodeReader
        //using ZXing;
        //using ZXing.Common;
        //using ZXing.Windows.Compatibility;
        BarcodeReader reader = new BarcodeReader();
        // 设置字符集和识别选项
        reader.Options = new DecodingOptions
        {
            CharacterSet = "UTF-8",
            // 添加其他优化设置（可选）
            TryHarder = true,   // 更耗时但更准确
            PossibleFormats = new List<BarcodeFormat> {
                    BarcodeFormat.QR_CODE,  // 根据实际类型添加
                    BarcodeFormat.CODE_128,
                    BarcodeFormat.CODE_39,
                    BarcodeFormat.EAN_8,
                    BarcodeFormat.EAN_13          }
        };
        // 执行识别
        Result result = reader.Decode(filename);
        return result?.Text ?? "";
    }
    /// <summary>
    /// 识别图片中第一个条形码/二维码，并返回其外接矩形
    /// </summary>
    /// <param name="imagePath">图片路径</param>
    /// <returns>条形码的外接矩形；未识别到则返回 Rectangle.Empty</returns>
    public static Rect DetectBarcodeRect(Bitmap bitmap)
    {
        

        var reader = new BarcodeReader
        {
            Options = new DecodingOptions
            {
                TryHarder = true,
                PossibleFormats = new[]
                {
                    BarcodeFormat.QR_CODE,
                    BarcodeFormat.CODE_128,
                    BarcodeFormat.CODE_39,
                    BarcodeFormat.EAN_8,
                    BarcodeFormat.EAN_13
                }
            }
        };

        var result = reader.Decode(bitmap);
        if (result == null) return new Rect();

        // result.ResultPoints 顺序：左上、右上、右下、左下（二维码）
        var points = result.ResultPoints;
        if (points.Length < 2) return new Rect();

        // 计算外接矩形
        float minX = points.Min(p => p.X);
        float maxX = points.Max(p => p.X);
        float minY = points.Min(p => p.Y);
        float maxY = points.Max(p => p.Y);

        return Rect.FromLTRB((int)minX, (int)minY, (int)maxX, (int)maxY);
    }
    /// <summary>
    /// 保留与大多数矩形特征一致的矩形
    /// </summary>
    /// <param name="rects">原始矩形列表</param>
    /// <param name="areaFactor">面积偏差阈值：|area - median| ≤ median * areaFactor</param>
    /// <param name="aspectFactor">长宽比偏差阈值：|aspect - median| ≤ median * aspectFactor</param>
    /// <returns>过滤后的矩形列表</returns>
    public static List<Rect> KeepConsensusRects(IReadOnlyList<Rect> rects,
                                                double areaFactor = 0.4,
                                                double aspectFactor = 0.3)
    {
        if (rects == null || rects.Count == 0)
            return new List<Rect>();

        // 1. 计算面积、长宽比
        var areas = rects.Select(r => r.Width * r.Height).ToList();
        var aspects = rects.Select(r => (double)r.Width / r.Height).ToList();

        // 2. 中位数
        areas.Sort();
        aspects.Sort();
        double medArea = areas[areas.Count / 2];
        double medAspect = aspects[aspects.Count / 2];

        // 3. 过滤
        return rects.Where(r =>
        {
            double a = r.Width * r.Height;
            double asp = (double)r.Width / r.Height;
            return Math.Abs(a - medArea) <= medArea * areaFactor &&
                   Math.Abs(asp - medAspect) <= medAspect * aspectFactor;
        }).ToList();
    }
    /// <summary>
    /// 只保留中心点密集的最大一簇矩形，剔除远离的矩形
    /// </summary>
    /// <param name="rects">输入矩形</param>
    /// <param name="eps">中心点之间的最大距离阈值（像素）</param>
    /// <param name="minPts">最小邻居数（≥2 即可）</param>
    public static List<Rect> KeepDenseRects(List<Rect> rects,
                                            double eps = 80, int minPts = 2)
    {
        if (rects == null || rects.Count == 0) return rects;

        // 1. 计算中心点
        var centers = rects.Select(r => new Point2d(r.X + r.Width / 2.0,
                                                    r.Y + r.Height / 2.0)).ToList();

        // 2. 简化版 DBSCAN：标记每个点属于哪个簇
        int[] labels = Enumerable.Repeat(-1, centers.Count).ToArray();
        int clusterId = 0;

        for (int i = 0; i < centers.Count; i++)
        {
            if (labels[i] != -1) continue; // 已访问

            var neighbors = Neighbors(centers, i, eps);
            if (neighbors.Count < minPts) continue; // 噪声

            // 新簇
            labels[i] = clusterId;
            var queue = new Queue<int>(neighbors);

            while (queue.Count > 0)
            {
                int q = queue.Dequeue();
                if (labels[q] == -1) labels[q] = clusterId; // 未访问
                else if (labels[q] != -1) continue;         // 已处理

                var n2 = Neighbors(centers, q, eps);
                if (n2.Count >= minPts)
                    foreach (int nIdx in n2) if (labels[nIdx] == -1) queue.Enqueue(nIdx);
            }
            clusterId++;
        }

        // 3. 最大簇
        int maxLabel = labels.GroupBy(l => l)
                             .OrderByDescending(g => g.Count())
                             .FirstOrDefault()?.Key ?? -1;

        return maxLabel == -1 ? rects
                              : rects.Where((_, i) => labels[i] == maxLabel).ToList();
    }

    // 找到中心点 idx 在 eps 内的所有索引
    private static List<int> Neighbors(List<Point2d> pts, int idx, double eps)
    {
        return pts.Select((p, i) => (p, i))
                  .Where(t => Point2d.Distance(pts[idx], t.p) <= eps)
                  .Select(t => t.i)
                  .ToList();
    }

}
/// <summary>
/// 基于矩形中心点的完整 DBSCAN 过滤器
/// </summary>
public static class RectDBSCAN
{
    /// <summary>
    /// 仅保留密集簇矩形（最大簇）
    /// </summary>
    /// <param name="rects">输入矩形</param>
    /// <param name="eps">邻域半径（像素）</param>
    /// <param name="minPts">成为核心点的最小邻居数</param>
    public static List<Rect> FilterDenseRects(List<Rect> rects,
                                              double eps = 70,
                                              int minPts = 3)
    {
        if (rects == null || rects.Count == 0) return rects;

        // 1. 计算中心点
        var centers = rects.Select(r => new Point2d(
            r.X + r.Width * 0.5,
            r.Y + r.Height * 0.5)).ToList();

        // 2. 标记：-1 未访问；>=0 簇号
        int[] labels = Enumerable.Repeat(-1, centers.Count).ToArray();
        int clusterId = 0;

        for (int i = 0; i < centers.Count; i++)
        {
            if (labels[i] != -1) continue;        // 已访问
            var neighbors = GetNeighbors(centers, i, eps);
            if (neighbors.Count < minPts) continue; // 噪声

            labels[i] = clusterId;
            var queue = new Queue<int>(neighbors);

            while (queue.Count > 0)
            {
                int q = queue.Dequeue();
                if (labels[q] == -1) labels[q] = clusterId;
                else if (labels[q] != -1) continue;

                var n2 = GetNeighbors(centers, q, eps);
                if (n2.Count >= minPts)
                    foreach (int nIdx in n2)
                        if (labels[nIdx] == -1) queue.Enqueue(nIdx);
            }
            clusterId++;
        }

        // 3. 取最大簇
        int maxLabel = labels.GroupBy(l => l)
                             .OrderByDescending(g => g.Count())
                             .Select(g => g.Key)
                             .FirstOrDefault(-1);

        return maxLabel == -1 ? rects
                              : rects.Where((_, i) => labels[i] == maxLabel).ToList();
    }

    private static List<int> GetNeighbors(List<Point2d> pts, int idx, double eps)
    {
        return pts.Select((p, i) => (p, i))
                  .Where(t => Point2d.Distance(pts[idx], t.p) <= eps)
                  .Select(t => t.i)
                  .ToList();
    }
    /// <summary>
    /// 把完全落在 insideRect 内部的矩形剔除
    /// </summary>
    public static List<Rect> SubtractInsideRects(IEnumerable<Rect> rects, Rect insideRect)
    {
        return rects.Where(r => !insideRect.Contains(r)).ToList();
    }
}